import es
import playerlib
import time

from wcs.libs.common import *
from wcs.libs.database import getDatabase
from wcs.libs.players.commands import CommandManager
from wcs.libs.players.cache import *

__all__ = ["players", "Player"]

class PlayerManager(object):
    """
    This class is used to manage players. This class itself simulates a
    dictionary but gives additional functions which allow us to modify the way
    the singelton is percived. This will store individual Player objects by a 
    tag name and allows us to reference the singleton object by dictionary item
    assignment / retrieval to retrieve the retrespective player object.
    """
    def __init__(self):
        """ Default constructor, assign the default values """
        self.players = {}
        self.nullPlayer = CustomNullType()
        
    def __contains__(self, userid):
        """
        Executed automatically when we use the syntax "x in object". We will
        test if the userid in question exists within the players dictionary.
        
        @param int|str userid The userid to test for validation
        @return bool Whether or not the userid already exists
        """
        return self.players.__contains__(str(userid))
        
    def __delitem__(self, userid):
        """
        Removes the player and object from memory so the RAM is cleared.
        object.removePlayer(userid) == object.__delitem__(userid)
        
        @param int|str userid The userid of the player to remove
        """
        userid = str(userid)
        if self.__contains__(userid):
            self.players.__delitem__(str(userid))
            
    def __getitem__(self, userid):
        """
        Executed when object[x] is executed on the singleton. As we wish to
        simulate dictionary attributes, we want to return the player object
        referenced by their userid
        
        @param int|str userid The ID of the user we wish to return the player
                              object for
        @return Player The Player object referenced by ID
        """
        userid = str(userid)
        if userid not in self.players:
            return self.nullPlayer
        return self.players[userid]
        
    def __iter__(self):
        """
        This function executes automatically when "for x in object" is executed.
        We want to return all the player objects inside the player dictionary.
        
        @return generator A generator to a list of all the player objects
        """
        return self.players.itervalues()
        
    def addPlayer(self, userid):
        """
        Adds a player object into the manager so we can reference it later. The
        tag name is the user's ID. This can be retrieved from every event.
        
        @param int|string userid The userid of the player to add
        """
        userid = str(userid)
        if not self.__contains__(userid):
            self.players[userid] = Player(userid)
        
    def removePlayer(self, userid):
        """
        Removes the player and object from memory so the RAM is cleared.
        object.removePlayer(userid) == object.__delitem__(userid)
        
        @param int|str userid The userid of the player to remove
        """
        self.__delitem__(userid)

class Player(object):
    """
    This is an object which will revolve around all active players. This should
    contain all relevant information about a particular player, and cache all
    their details so we don't need to query the database at regular intervals.
    """
    cache = None
    command = None
    def __init__(self, userid):
        """
        Default constructor, ensure that default values are assigned. Set up all
        necessary objects and cache values here.
        
        @param int|str userid The ID of the user who this object references
        """
        from wcs.libs.races import races
        
        self.userid  = str(userid)
        self.steamid = playerlib.uniqueid(userid, True)
        self.index   = playerlib.getPlayer(userid).index

        database = getDatabase()

        # Obtain the dbUserid from the database using the steamid
        database.execute("""SELECT userID FROM player WHERE steamID=?""",
                                                                   self.steamid)
        results = database.fetchone()
        if results is None:
            database.execute("""INSERT INTO player (level, steamid, name, race,
                            lastConnected) VALUES (?, ?, ?, ?, ?)""",
                            1, self.steamid, es.getplayername(self.userid),
                            races.raceOrder[0], time.time())
            self.dbUserid = database.cursor.lastrowid
        else:
            self.dbUserid = results


        dict.__setattr__(self, "command", CommandManager(self.userid, self))
        dict.__setattr__(self, "cache", PlayerCache(userid, self.dbUserid))
        self.cache.refresh()
    
    def __del__(self):
        """
        Default deconstructor, executed when no more references to this object
        are left. Ensure that the cache object is also cleared up. Although the
        scopes should clear it up, explicit is better than implicity. <3 C style
        memory management.
        """
        del self.cache
        
    def __contains__(self, key):
        """
        Executed when we wish to see if an item is cotnained within this object.
        
        @param str key The name of the key to test for validity
        """
        if key in self.command:
            return True
        if key in self.cache:
            return True

    def __getattr__(self, key):
        """
        Executed when we attempt to retrieve an object from this player object
        which isn't recognised. Assume we want to retrieve it from the player
        cache or command object.

        @param string key The name of the attribute
        @return object The attribute's value from the cache object
        """
        if hasattr(self.cache, key):
            return getattr(self.cache, key)
        elif hasattr(self.command, key):
            return getattr(self.command, key)
        raise AttributeError("Player '%s' does not have the '%s' object" % (
                                                                self.name))

    def __setattr__(self, key, value):
        """
        Executed when an unkown object is attempting to have its attribute
        assigned. We'll first see if the attribute belongs in the player cache.
        If so, we'll assign it to the cache; otherwise assign it to this object.

        @param string key The attribute name
        @param object value The value of the attribute
        """
        if self.cache is not None:
            if hasattr(self.cache, key):
                setattr(self.cache, key, value)
        elif self.command is not None:
            if hasattr(self.command, key):
                setattr(self.command, key, value)
        else:
            object.__setattr__(self, key, value)

    def __getitem__(self, key):
        """
        Executed when we attempt to retreive an object from this player's object
        which isn't recognised. Assume we want to retrieve it from the player
        cache.

        @param string key The name of the attribute / Race
        @return mixed The race's Cache object / attribute's value.
        """
        from wcs.libs.common import CustomNullType
        
        if not isinstance(key, str):
            return CustomNullType()
        if key in self.cache:
            return self.cache[key]
        return self.command[key]

    def __setitem__(self, key, value):
        """
        Executed when we attempt to set an object to a given key to this
        player's instance. Assign it to the player's attribute instance in
        the command object.

        @param object key The item of which to assign the value
        @param object value The value which is representated by key
        """
        self.command[key] = value
        
    def __delitem__(self, key):
        """
        Executed when we attempt to remove an attribute from this object
        referenced by the relevant key. Assume that we want to remove an
        attribute directly from the command object as we are only customly
        overwriting the attribute object within the command cache.
        
        @param object key The item of the value which we wish to remove
        """
        del self.command[key]
        
players = PlayerManager()